<!doctype html>



  


<html class="theme-next mist use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>






<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />




  
  
  
  

  
    
    
  

  

  

  

  

  
    
    
    <link href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
  






<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.1" rel="stylesheet" type="text/css" />


  <meta name="keywords" content="Java EE," />





  <link rel="alternate" href="/atom.xml" title="Bingo's Blog" type="application/atom+xml" />




  <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=5.1.1" />






<meta name="description" content="前言当后台项目使用 JavaWeb 进行开发时，写接口就成为一门艺术活。无论是后台管理系统的前后端分离、还是要给移动端提供数据，返回方便、规范的数据及其重要。掌握以后，你肯定会爱不释手。有一年以上 Java 开发经验的人，相信不会再选择 PHP、Nodejs 等来写接口，原因只能意会，不可言传。">
<meta name="keywords" content="Java EE">
<meta property="og:type" content="article">
<meta property="og:title" content="JavaWeb 基于Maven+SSM 提供API服务">
<meta property="og:url" content="https://bingozb.github.io/36.html">
<meta property="og:site_name" content="Bingo's Blog">
<meta property="og:description" content="前言当后台项目使用 JavaWeb 进行开发时，写接口就成为一门艺术活。无论是后台管理系统的前后端分离、还是要给移动端提供数据，返回方便、规范的数据及其重要。掌握以后，你肯定会爱不释手。有一年以上 Java 开发经验的人，相信不会再选择 PHP、Nodejs 等来写接口，原因只能意会，不可言传。">
<meta property="og:image" content="https://blog.bingo.ren/images/blog/36/demo.png">
<meta property="og:updated_time" content="2017-09-22T17:30:21.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="JavaWeb 基于Maven+SSM 提供API服务">
<meta name="twitter:description" content="前言当后台项目使用 JavaWeb 进行开发时，写接口就成为一门艺术活。无论是后台管理系统的前后端分离、还是要给移动端提供数据，返回方便、规范的数据及其重要。掌握以后，你肯定会爱不释手。有一年以上 Java 开发经验的人，相信不会再选择 PHP、Nodejs 等来写接口，原因只能意会，不可言传。">
<meta name="twitter:image" content="https://blog.bingo.ren/images/blog/36/demo.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Mist',
    sidebar: {"position":"left","display":"post","offset":12,"offset_float":0,"b2t":false,"scrollpercent":false},
    fancybox: true,
    motion: true,
    duoshuo: {
      userId: 'undefined',
      author: '博主'
    },
    algolia: {
      applicationID: 'JE30EVH7NI',
      apiKey: '862bb3deb9a3166ed99de0be6527f59c',
      indexName: 'blog',
      hits: {"per_page":10},
      labels: {"input_placeholder":"输入关键词进行搜索","hits_empty":"找不到关于「 ${query} 」的文章","hits_stats":"共找到 ${hits} 篇文章，花了 ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://bingozb.github.io/36.html"/>





  <title>JavaWeb 基于Maven+SSM 提供API服务 | Bingo's Blog</title>
</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  





  <script type="text/javascript">
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?c8a59b9753bce3ec58f81c89e2acce96";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>










  
  
    
  

  <div class="container sidebar-position-left page-post-detail ">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Bingo's Blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">Stay Hungry, Stay Foolish.</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />
            
            关于
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br />
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocapitalize="off" autocomplete="off" autocorrect="off"
             placeholder="搜索..." spellcheck="false"
             type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://bingozb.github.io/36.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Bingo">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Bingo's Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                JavaWeb 基于Maven+SSM 提供API服务
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-04-08T18:49:51+08:00">
                2017-04-08
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/战斗笔记/" itemprop="url" rel="index">
                    <span itemprop="name">战斗笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
              <span class="post-meta-divider">|</span>
              <span class="post-meta-item-icon">
                <i class="fa fa-comment-o"></i>
              </span>
              
                <a href="/36.html#SOHUCS" itemprop="discussionUrl">
                  <span id="changyan_count_unit" class="post-comments-count hc-comment-count" data-xid="36.html" itemprop="commentsCount"></span>
                </a>
              
            
          

          
          
             <span id="/36.html" class="leancloud_visitors" data-flag-title="JavaWeb 基于Maven+SSM 提供API服务">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数 </span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    <div class="post-body" itemprop="articleBody">

      
      

      
        <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>当后台项目使用 JavaWeb 进行开发时，写接口就成为一门艺术活。无论是后台管理系统的前后端分离、还是要给移动端提供数据，返回方便、规范的数据及其重要。掌握以后，你肯定会爱不释手。有一年以上 Java 开发经验的人，相信不会再选择 PHP、Nodejs 等来写接口，原因只能意会，不可言传。</p>
<a id="more"></a>
<h2 id="规定与约束"><a href="#规定与约束" class="headerlink" title="规定与约束"></a>规定与约束</h2><ul>
<li><p>开发工具：IntelliJ IDEA</p>
</li>
<li><p>开发语言：Java</p>
</li>
<li><p>依赖管理：Maven</p>
</li>
<li><p>整合框架：Spring 4.1.4 + SpringMVC 4.1.4 + Mybatis 3.4.1</p>
</li>
<li><p>架构：MMCS（Mapper、Model、Controller、Service）</p>
<ul>
<li>Mapper 是对象持久化映射层，使用 Mybatis 进行数据库交互</li>
<li>Model 是数据模型层相当于 MVC 的 M层，存放 POJO 类</li>
<li>Controller 是控制层，相当于 MVC 的 C层</li>
<li>Servive 是一些业务逻辑的处理层</li>
</ul>
</li>
</ul>
<h2 id="核心配置"><a href="#核心配置" class="headerlink" title="核心配置"></a>核心配置</h2><p>三大框架的整合网上例子很多，这里只阐述针对写 API 的核心点。</p>
<h3 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h3><p>pom.xml 添加 Gson 依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.google.code.gson<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>gson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.8.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></div></pre></td></tr></table></figure>
<p>添加 Aspectj 依赖。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">&lt;!-- AOP --&gt;</span></div><div class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.aspectj<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>aspectjweaver<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.7.4<span class="tag">&lt;/<span class="name">version</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></div><div class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>aspectj<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>aspectjrt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.5.4<span class="tag">&lt;/<span class="name">version</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></div></pre></td></tr></table></figure>
<h3 id="Spring-MVC-配置"><a href="#Spring-MVC-配置" class="headerlink" title="Spring MVC 配置"></a>Spring MVC 配置</h3><p>修改 SpringMVC 的配置文件，对应 Demo 中的 spring-mvc.xml。</p>
<h4 id="开启注解模式驱动"><a href="#开启注解模式驱动" class="headerlink" title="开启注解模式驱动"></a>开启注解模式驱动</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">mvc:annotation-driven</span>/&gt;</span></div></pre></td></tr></table></figure>
<p>由于 JSON 需要配置 AnnotationMethodHandlerAdapter 和合适的 HttpMessageConverter，Spring 4.1 提供了 Gson 的 HttpMessageConverter，设置此标签后，并且已经依赖了 Gson，就已经完成了 JSON 的配置。</p>
<p>相当于注册了 DefaultAnnotationHandlerMapping 和 AnnotationMethodHandlerAdapter 两个 bean，配置一些 messageConverter 为 GsonHttpMessageConverter，解决了 @Controller 注解的使用前提配置。</p>
<h4 id="启动自动扫包"><a href="#启动自动扫包" class="headerlink" title="启动自动扫包"></a>启动自动扫包</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">"cn.dankal.web"</span>&gt;</span></div><div class="line">    <span class="comment">&lt;!-- 制定扫包规则,不扫描使用@Service注解的JAVA类 --&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">context:exclude-filter</span> <span class="attr">type</span>=<span class="string">"annotation"</span> <span class="attr">expression</span>=<span class="string">"org.springframework.stereotype.Service"</span>/&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">context:component-scan</span>&gt;</span></div></pre></td></tr></table></figure>
<p>注意，这里不能扫描<code>@Service</code>注解的 Java 类，因为 <code>spring-mvc.xml</code> 与 <code>spring-mybatis.xml</code> 不是同时加载，如果不进行这样的设置，那么 spring 就会将所有带 <code>@Service</code> 注解的类都扫描到容器中，等到加载 <code>spring-mybatis.xml</code> 的时候，会因为容器已经存在 Service 类，使得 cglib 不对 Service 进行代理，直接导致的结果就是在 <code>spring-mybatis.xml</code> 中的事务配置不起作用，发生异常时，无法对数据进行回滚，在这里也会导致 APILogger 无法正常切入。</p>
<h4 id="删除视图解析器"><a href="#删除视图解析器" class="headerlink" title="删除视图解析器"></a>删除视图解析器</h4><p>由于只提供 API 服务，不需要做页面跳转，所不需要视图解析器（ViewResolver），相比普通的 Web 项目，可以<strong>不配置</strong>这个 bean。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">bean</span>  </span></div><div class="line">   <span class="attr">class</span>=<span class="string">"org.springframework.web.servlet.view.InternalResourceViewResolver"</span>&gt;  </div><div class="line">   <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"suffix"</span> <span class="attr">value</span>=<span class="string">".jsp"</span> /&gt;</span>  </div><div class="line">   ... </div><div class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></div></pre></td></tr></table></figure>
<h3 id="Spring-配置"><a href="#Spring-配置" class="headerlink" title="Spring 配置"></a>Spring 配置</h3><p>启动 Aspectj 注解模式驱动 AOP</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">aop:aspectj-autoproxy</span> <span class="attr">proxy-target-class</span>=<span class="string">"true"</span>/&gt;</span></div></pre></td></tr></table></figure>
<p><code>proxy-target-class=&quot;true&quot;</code> 是强制使用 CGLIB 进行动态代理，如果不添加，则是 JDK 动态代理，反射的效率不是很高。</p>
<p>使用 CGLib 实现动态代理，完全不受代理类必须实现接口的限制，而且 CGLib 底层采用 ASM 字节码生成框架，使用字节码技术生成代理类，比使用 Java 反射效率要高。唯一需要注意的是，CGLib 不能对声明为 final 的方法进行代理，因为 CGLib 原理是动态生成被代理类的子类。</p>
<h2 id="实现设计"><a href="#实现设计" class="headerlink" title="实现设计"></a>实现设计</h2><h3 id="Json数据格式规范"><a href="#Json数据格式规范" class="headerlink" title="Json数据格式规范"></a>Json数据格式规范</h3><h4 id="正确结果的响应"><a href="#正确结果的响应" class="headerlink" title="正确结果的响应"></a>正确结果的响应</h4><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">&#123;</div><div class="line">    <span class="attr">"state"</span>: <span class="string">"00001"</span>, </div><div class="line">    <span class="attr">"message"</span>: <span class="string">"success"</span>, </div><div class="line">    <span class="attr">"result"</span>: &#123;</div><div class="line">        <span class="attr">"id"</span>: <span class="number">1</span>,</div><div class="line">        <span class="attr">"username"</span>: <span class="string">"bingo"</span>,</div><div class="line">        <span class="attr">"enable"</span>: <span class="literal">true</span>,</div><div class="line">        <span class="attr">"create_time"</span>: <span class="string">"1483082840732"</span></div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h4 id="错误结果的响应"><a href="#错误结果的响应" class="headerlink" title="错误结果的响应"></a>错误结果的响应</h4><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">&#123;</div><div class="line">    <span class="attr">"state"</span>: <span class="string">"00002"</span>, </div><div class="line">    <span class="attr">"message"</span>: <span class="string">"用户名密码错误"</span></div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h4 id="字段说明"><a href="#字段说明" class="headerlink" title="字段说明"></a>字段说明</h4><table>
<thead>
<tr>
<th>字段</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>state</td>
<td>状态码，例如 00001 为成功状态，其它状态码为错误码</td>
</tr>
<tr>
<td>message</td>
<td>消息提示，例如 success 为成功，其它情况为错误原因</td>
</tr>
<tr>
<td>result</td>
<td>结果数据</td>
</tr>
</tbody>
</table>
<h3 id="API工具类封装"><a href="#API工具类封装" class="headerlink" title="API工具类封装"></a>API工具类封装</h3><h4 id="APIStatus"><a href="#APIStatus" class="headerlink" title="APIStatus"></a>APIStatus</h4><p>由于状态对应的状态码和消息都属于常量，所以抽成一个文件来统一管理枚举。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"> * API状态枚举</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="keyword">enum</span> APIStatus &#123;</div><div class="line"></div><div class="line">    API_SUCCESS(<span class="string">"00001"</span>, <span class="string">"success"</span>),</div><div class="line">    API_USER_PASSWORD_ERROR(<span class="string">"00002"</span>, <span class="string">"用户名密码错误"</span>),</div><div class="line">    API_USER_NOT_EXIST(<span class="string">"00003"</span>, <span class="string">"用户名不存在"</span>);</div><div class="line"></div><div class="line">    <span class="keyword">private</span> String state;</div><div class="line">    <span class="keyword">private</span> String message;</div><div class="line"></div><div class="line">    APIStatus(String state, String message) &#123;</div><div class="line">        <span class="keyword">this</span>.state = state;</div><div class="line">        <span class="keyword">this</span>.message = message;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getState</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> state;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getMessage</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> message;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h4 id="APIRequest"><a href="#APIRequest" class="headerlink" title="APIRequest"></a>APIRequest</h4><p>APIRequest 对 HttpServletRequest 进行封装，提供常用的属性包括请求的URL、请求参数等。如果提供的属性不能满足需求，还提供了原 HttpServletRequest 对象属性 request。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div><div class="line">96</div><div class="line">97</div><div class="line">98</div><div class="line">99</div><div class="line">100</div><div class="line">101</div><div class="line">102</div><div class="line">103</div><div class="line">104</div><div class="line">105</div><div class="line">106</div><div class="line">107</div><div class="line">108</div><div class="line">109</div><div class="line">110</div><div class="line">111</div><div class="line">112</div><div class="line">113</div><div class="line">114</div><div class="line">115</div><div class="line">116</div><div class="line">117</div><div class="line">118</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"> * API请求实体类</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">APIRequest</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="comment">/** Raw Request, 单元测试时为null */</span></div><div class="line">    <span class="keyword">private</span> HttpServletRequest request;</div><div class="line"></div><div class="line">    <span class="comment">/** 请求头 */</span></div><div class="line">    <span class="keyword">private</span> Map&lt;String, Object&gt; header;</div><div class="line"></div><div class="line">    <span class="comment">/** 请求参数 */</span></div><div class="line">    <span class="keyword">private</span> Map&lt;String, Object&gt; params;</div><div class="line"></div><div class="line">    <span class="comment">/** 客户端发出请求时的完整URL */</span></div><div class="line">    <span class="keyword">private</span> String url;</div><div class="line"></div><div class="line">    <span class="comment">/** 请求行中的资源名部分 */</span></div><div class="line">    <span class="keyword">private</span> String uri;</div><div class="line"></div><div class="line">    <span class="comment">/** 请求行中的参数部分 */</span></div><div class="line">    <span class="keyword">private</span> String queryString;</div><div class="line"></div><div class="line">    <span class="comment">/** 请求方法 */</span></div><div class="line">    <span class="keyword">private</span> String method;</div><div class="line"></div><div class="line">    <span class="comment">/** 发出请求的客户机的IP地址 */</span></div><div class="line">    <span class="keyword">private</span> String remoteAddr;</div><div class="line"></div><div class="line">    <span class="comment">/** 发出请求的客户机的完整主机名 */</span></div><div class="line">    <span class="keyword">private</span> String remoteHost;</div><div class="line"></div><div class="line">    <span class="comment">/** 发出请求的客户机的网络端口号 */</span></div><div class="line">    <span class="keyword">private</span> <span class="keyword">int</span> remotePort;</div><div class="line"></div><div class="line">    <span class="comment">/** WEB服务器的IP地址 */</span></div><div class="line">    <span class="keyword">private</span> String localAddr;</div><div class="line"></div><div class="line">    <span class="comment">/** WEB服务器的主机名 */</span></div><div class="line">    <span class="keyword">private</span> String localName;</div><div class="line"></div><div class="line">    <span class="comment">/** WEB服务器的网络端口号 */</span></div><div class="line">    <span class="keyword">private</span> <span class="keyword">int</span> localPort;</div><div class="line"></div><div class="line">    <span class="comment">/** 编码格式 */</span></div><div class="line">    <span class="keyword">private</span> String characterEncoding;</div><div class="line"></div><div class="line">    <span class="comment">/** 上下文 */</span></div><div class="line">    <span class="keyword">private</span> String contextPath;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">APIRequest</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">super</span>();</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">APIRequest</span><span class="params">(HttpServletRequest request)</span> </span>&#123;</div><div class="line"></div><div class="line">        <span class="keyword">this</span>.request = request;</div><div class="line"></div><div class="line">        header = <span class="keyword">new</span> HashMap&lt;String, Object&gt;();</div><div class="line">        Enumeration e = request.getHeaderNames();</div><div class="line">        <span class="keyword">while</span> (e.hasMoreElements()) &#123;</div><div class="line">            String name = (String) e.nextElement();</div><div class="line">            Object value = request.getHeader(name);</div><div class="line">            header.put(name, value);</div><div class="line">        &#125;</div><div class="line"></div><div class="line">        params = getParamesMap(request.getParameterMap());</div><div class="line">        url = request.getRequestURL().toString();</div><div class="line">        uri = request.getRequestURI();</div><div class="line">        remoteAddr = request.getRemoteAddr();</div><div class="line">        remoteHost = request.getRemoteHost();</div><div class="line">        remotePort = request.getRemotePort();</div><div class="line">        contextPath = request.getContextPath();</div><div class="line">        localAddr = request.getLocalAddr();</div><div class="line">        characterEncoding = request.getCharacterEncoding();</div><div class="line">        localName = request.getLocalName();</div><div class="line">        localPort = request.getLocalPort();</div><div class="line">        method = request.getMethod();</div><div class="line">        queryString = request.getQueryString();</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">private</span> Map&lt;String, Object&gt; <span class="title">getParamesMap</span><span class="params">(Map properties)</span> </span>&#123;</div><div class="line">        Map&lt;String, Object&gt; returnMap = <span class="keyword">new</span> HashMap&lt;String, Object&gt;();</div><div class="line">        Iterator entries = properties.entrySet().iterator();</div><div class="line">        Map.Entry entry;</div><div class="line">        String name = <span class="string">""</span>;</div><div class="line">        String value = <span class="string">""</span>;</div><div class="line">        <span class="keyword">while</span> (entries.hasNext()) &#123;</div><div class="line">            entry = (Map.Entry) entries.next();</div><div class="line">            name = (String) entry.getKey();</div><div class="line">            Object valueObj = entry.getValue();</div><div class="line">            <span class="keyword">if</span> (<span class="keyword">null</span> == valueObj) &#123;</div><div class="line">                value = <span class="string">""</span>;</div><div class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (valueObj <span class="keyword">instanceof</span> String[]) &#123;</div><div class="line">                String[] values = (String[]) valueObj;</div><div class="line">                <span class="keyword">for</span> (String v : values) &#123;</div><div class="line">                    value = v + <span class="string">","</span>;</div><div class="line">                &#125;</div><div class="line">                value = value.substring(<span class="number">0</span>, value.length() - <span class="number">1</span>);</div><div class="line">            &#125; <span class="keyword">else</span> &#123;</div><div class="line">                value = valueObj.toString();</div><div class="line">            &#125;</div><div class="line">            returnMap.put(name, value);</div><div class="line">        &#125;</div><div class="line">        <span class="keyword">return</span> returnMap;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">getParameter</span><span class="params">(String s)</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> params.get(s);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setAttribute</span><span class="params">(String s, Object o)</span> </span>&#123;</div><div class="line">        <span class="keyword">if</span> (params == <span class="keyword">null</span>) params = <span class="keyword">new</span> HashMap&lt;String, Object&gt;();</div><div class="line">        params.put(s, o.toString());</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="comment">// getters and setters ...</span></div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h4 id="APIResponse"><a href="#APIResponse" class="headerlink" title="APIResponse"></a>APIResponse</h4><p>把 API 响应数据封装为实体类，并提供了单例对象的获取方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"> * API响应实体类</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">APIResponse</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> String state;</div><div class="line">    <span class="keyword">private</span> String message;</div><div class="line">    <span class="keyword">private</span> Object result;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getState</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> state;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setState</span><span class="params">(String state)</span> </span>&#123;</div><div class="line">        <span class="keyword">this</span>.state = state;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getMessage</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> message;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setMessage</span><span class="params">(String message)</span> </span>&#123;</div><div class="line">        <span class="keyword">this</span>.message = message;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">getResult</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> result;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setResult</span><span class="params">(Object result)</span> </span>&#123;</div><div class="line">        <span class="keyword">this</span>.result = result;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="comment">/**</span></div><div class="line">     * 获取 APIResponse 单例对象</div><div class="line">     *</div><div class="line">     * <span class="doctag">@return</span> 单例API工具对象</div><div class="line">     */</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> APIResponse <span class="title">getInstance</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> APIResponseHolder.instance;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">private</span> <span class="title">APIResponse</span><span class="params">()</span> </span>&#123;&#125;</div><div class="line"></div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">APIResponseHolder</span> </span>&#123;</div><div class="line">        <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> APIResponse instance = <span class="keyword">new</span> APIResponse();</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h4 id="APIUtil"><a href="#APIUtil" class="headerlink" title="APIUtil"></a>APIUtil</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"> * API工具类</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">APIUtil</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="comment">/**</span></div><div class="line">     * 获取 APIResponse 对象</div><div class="line">     *</div><div class="line">     * <span class="doctag">@param</span> apiStatus 接口状态枚举值</div><div class="line">     * <span class="doctag">@param</span> result    接口结果</div><div class="line">     * <span class="doctag">@return</span> APIResponse 单例对象</div><div class="line">     */</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> APIResponse <span class="title">getResponse</span><span class="params">(APIStatus apiStatus, Object result)</span> </span>&#123;</div><div class="line">        APIResponse response = APIResponse.getInstance();</div><div class="line">        response.setState(apiStatus.getState());</div><div class="line">        response.setMessage(apiStatus.getMessage());</div><div class="line">        response.setResult(apiStatus == API_SUCCESS ? result : <span class="keyword">null</span>);</div><div class="line"></div><div class="line">        <span class="keyword">return</span> response;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>APIUtil 作为工具类，原本我封装了 Gson 的相关方法，但后来使用 Spring 的 @ResponseBody 就只需要返回一个 POJO 了，所以，该工具类暂时只提供一个方法。</p>
<p>值得一提的是，返回的 APIResponse 是一个单例对象，虽然 Java 有垃圾自动回收机制，但我个人还是觉得没有必要每个 API 请求都 new 一个 APIResponse 来转换 JSON，用单例会比较合适。</p>
<h4 id="APILogger"><a href="#APILogger" class="headerlink" title="APILogger"></a>APILogger</h4><p>使用 AOP 技术，编写 Service 层的切面，切点为 <code>cn.dankal.web.service</code> 包及子包下的文件。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"> * API日志管理器</div><div class="line"> * AOP技术 Service层的日志切面</div><div class="line"> */</div><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="meta">@Aspect</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">APILogger</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> Logger logger = Logger.getLogger(<span class="keyword">this</span>.getClass());</div><div class="line"></div><div class="line">    <span class="meta">@Pointcut</span>(<span class="string">"execution(* cn.dankal.web.service..*(..))"</span>)</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">apiPointcut</span><span class="params">()</span> </span>&#123;&#125;</div><div class="line"></div><div class="line">    <span class="comment">// 前置通知</span></div><div class="line">    <span class="meta">@Before</span>(<span class="string">"apiPointcut()"</span>)</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">before</span><span class="params">(JoinPoint joinPoint)</span> </span>&#123;</div><div class="line">        Object obj[] = joinPoint.getArgs();</div><div class="line">        <span class="keyword">if</span> (obj.length &gt; <span class="number">0</span>) &#123;</div><div class="line">            APIRequest request = (APIRequest) obj[<span class="number">0</span>];</div><div class="line">            Set set = request.getParams().entrySet();</div><div class="line">            Map.Entry[] entries = (Map.Entry[]) set.toArray(<span class="keyword">new</span> Map.Entry[set.size()]);</div><div class="line">            <span class="keyword">for</span> (Map.Entry entry : entries) &#123;</div><div class="line">                logger.info(<span class="string">"[Params] "</span> + entry.getKey() + <span class="string">":"</span> + entry.getValue());</div><div class="line">            &#125;</div><div class="line">        &#125; <span class="keyword">else</span> &#123;</div><div class="line">            logger.info(<span class="string">"[Params] null"</span>);</div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="comment">// 后置返回通知</span></div><div class="line">    <span class="meta">@AfterReturning</span>(pointcut = <span class="string">"apiPointcut()"</span>, argNames = <span class="string">"joinPoint, response"</span>, returning = <span class="string">"response"</span>)</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterReturn</span><span class="params">(JoinPoint joinPoint, APIResponse response)</span> </span>&#123;</div><div class="line">        logger.info(joinPoint + <span class="string">" Response: "</span> + <span class="keyword">new</span> Gson().toJson(response) + <span class="string">"\n"</span>);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="comment">// 抛出异常后通知</span></div><div class="line">    <span class="meta">@AfterThrowing</span>(pointcut = <span class="string">"apiPointcut()"</span>, throwing = <span class="string">"ex"</span>)</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterThrow</span><span class="params">(JoinPoint joinPoint, Exception ex)</span> </span>&#123;</div><div class="line">        logger.error(joinPoint + <span class="string">" Exception: "</span> + ex.getMessage());</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在前置通知（@Before）中，将 Service 层的方法的参数进行日志打印。由于已经设计了所有 Service 层的方法的参数都是 <code>无</code> 或者 <code>APIRequest对象</code>，所以在前置通知中通过连接点拿到的参数，如果有值，必然为 APIRequest 对象，然后遍历打印它的 params 属性输出请求参数。</p>
<p>在后置返回通知（@AfterReturning）中，将 Service 层的方法的返回值进行日志打印。由于已经设计了所有 Service 层的方法的返回值都是 APIResponse 对象，所以可以直接用 Gson 将其序列化为 Json 字符串并打印输出。</p>
<p>在抛出异常后的通知（@AfterThrowing）中，将连接点和异常进行日志打印。</p>
<p>由此，所有的 Service 层的方法都会在执行后输出日志，包括接口请求的参数和响应的结果 Json，而 Service 层不需要写任何 log 语句。</p>
<h3 id="Model层设计"><a href="#Model层设计" class="headerlink" title="Model层设计"></a>Model层设计</h3><p>与普通的 POJO 类一样，包含了数据库表对应的字段，值得一提的是 <code>transient</code> 这个关键字，是 Gson 中过滤序列化/反序列化的一种方法。</p>
<p>例如，用户的密码在登录的 API 不参与序列化，可以给 <code>password</code> 这个属性添加 <code>transient</code> 关键字。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> Integer id;</div><div class="line">    <span class="keyword">private</span> String username;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> String password;</div><div class="line">    <span class="keyword">private</span> Boolean enable;</div><div class="line">    <span class="keyword">private</span> String role;</div><div class="line">    <span class="keyword">private</span> String last_time;</div><div class="line">    <span class="keyword">private</span> String create_time;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">User</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">super</span>();</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">User</span><span class="params">(String username, String password)</span> </span>&#123;</div><div class="line">        <span class="keyword">super</span>();</div><div class="line">        <span class="keyword">this</span>.username = username;</div><div class="line">        <span class="keyword">this</span>.password = password;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="comment">// getters and setters ...</span></div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="Mapper层设计"><a href="#Mapper层设计" class="headerlink" title="Mapper层设计"></a>Mapper层设计</h3><p>与普通的 Mapper 一样，Demo 中我为了简便，使用了 Mybatis 的注解的方式，做数据库查询操作。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserMapper</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="meta">@Select</span>(<span class="string">"select * from user where username=#&#123;username&#125;"</span>)</div><div class="line">    <span class="function">User <span class="title">selectUserByUsername</span><span class="params">(@Param(<span class="string">"username"</span>)</span> String username)</span>;</div><div class="line"></div><div class="line">    <span class="meta">@Select</span>(<span class="string">"select * from user"</span>)</div><div class="line">    <span class="function">List&lt;User&gt; <span class="title">selectAllUser</span><span class="params">()</span></span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="Service层设计"><a href="#Service层设计" class="headerlink" title="Service层设计"></a>Service层设计</h3><h4 id="接口"><a href="#接口" class="headerlink" title="接口"></a>接口</h4><ul>
<li><p>返回值</p>
<p>  Service 层所有的方法统一返回 APIResponse 对象。</p>
</li>
<li><p>参数</p>
<ul>
<li>当接口请求有传值时，方法的参数为 APIRequest 对象；</li>
<li>当接口请求不需要传值时，一般为 GET 请求，此时方法不需要参数。</li>
</ul>
</li>
</ul>
<p>eg.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserService</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="comment">/**</span></div><div class="line">     * 登录</div><div class="line">     */</div><div class="line">    <span class="function">APIResponse <span class="title">login</span><span class="params">(APIRequest request)</span></span>;</div><div class="line"></div><div class="line">    <span class="comment">/**</span></div><div class="line">     * 获取所有用户</div><div class="line">     */</div><div class="line">    <span class="function">APIResponse <span class="title">allUsers</span><span class="params">()</span></span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h4 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h4><p>实现类是整个架构中代码量最多的一部分，所有的业务逻辑处理全部都放在 ServiceImpl，包括请求参数值的获取、业务逻辑处理、最后返回 APIResponse 对象。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title">UserService</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="meta">@Resource</span></div><div class="line">    <span class="keyword">private</span> UserMapper mapper;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> APIResponse <span class="title">login</span><span class="params">(APIRequest request)</span> </span>&#123;</div><div class="line">        <span class="comment">// 获取请求参数</span></div><div class="line">        String username = (String)request.getParameter(<span class="string">"username"</span>);</div><div class="line">        String password = (String)request.getParameter(<span class="string">"password"</span>);</div><div class="line">        <span class="comment">// 处理业务逻辑</span></div><div class="line">        User user = mapper.selectUserByUsername(username);</div><div class="line">        APIStatus status = API_SUCCESS;</div><div class="line">        <span class="keyword">if</span> (user == <span class="keyword">null</span>) &#123;</div><div class="line">            status = API_USER_NOT_EXIST;</div><div class="line">        &#125; <span class="keyword">else</span> &#123;</div><div class="line">            <span class="keyword">if</span> (!MD5Util.md5(password).equals(user.getPassword()))</div><div class="line">                status = API_USER_PASSWORD_ERROR;</div><div class="line">        &#125;</div><div class="line">        <span class="comment">// 返回APIResponse对象，配合@ResponseBody转为Json</span></div><div class="line">        <span class="keyword">return</span> APIUtil.getResponse(status, user);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> APIResponse <span class="title">allUsers</span><span class="params">()</span> </span>&#123;</div><div class="line">        List&lt;User&gt; users = mapper.selectAllUser();</div><div class="line">        <span class="keyword">return</span> APIUtil.getResponse(API_SUCCESS, users);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="Controller-层设计"><a href="#Controller-层设计" class="headerlink" title="Controller 层设计"></a>Controller 层设计</h3><p>基于前面的设计，此时的 Controller 层变得非常轻量级。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@RequestMapping</span>(<span class="string">"user"</span>)</div><div class="line"><span class="meta">@Controller</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="meta">@Resource</span></div><div class="line">    <span class="keyword">private</span> UserService userService;</div><div class="line"></div><div class="line">    <span class="meta">@RequestMapping</span>(value = <span class="string">"/login"</span>, method = RequestMethod.POST, headers = <span class="string">"api-version=1"</span>)</div><div class="line">    <span class="keyword">public</span> <span class="meta">@ResponseBody</span></div><div class="line">    <span class="function">APIResponse <span class="title">login</span><span class="params">(HttpServletRequest request)</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> userService.login(<span class="keyword">new</span> APIRequest(request));</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="meta">@RequestMapping</span>(value = <span class="string">"/query"</span>, method = RequestMethod.GET, headers = <span class="string">"api-version=1"</span>)</div><div class="line">    <span class="keyword">public</span> <span class="meta">@ResponseBody</span></div><div class="line">    <span class="function">APIResponse <span class="title">query</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> userService.allUsers();</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>其中，@RequestMapping 除了绑定路由，headers 还规定一定要有 <code>api-version=1</code>（参考） 这对键值对，这是接口的版本控制，在迭代开发中是非常重要的。</p>
<p>方法的返回值为 APIResponse，要加上注解 <code>@ResponseBody</code>，作用是将返回的对象作为 HTTP 响应正文返回，并调用 GsonHttpMessageConverter 这个适配器转换对象写入输出流。</p>
<p><strong>为什么不直接将 HttpServletRequest 对象传给 Service 层方法呢？</strong></p>
<p>因为 HttpServletRequest 是接口，SUN 定义了 J2EE 的所有接口，由各个 Application Server 的厂商自己实现。HttpServletRequest 本身并没有构造方法，无法实例化，必须在容器运行环境的情况下才能拿到它。</p>
<p>所以，APIRequest 对 HttpServletRequest 对象进行封装，对其添加构造方法，使得我们可以实例化一个 APIRequest 对象，可以在容器（Tomcat）不运行的情况下可以进行单元测试。</p>
<h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><p>进行 Service 层的单元测试，跑登录的测试用例，并根据期望结果进行断言。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Test</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">login</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</div><div class="line">    <span class="comment">// 正常登录</span></div><div class="line">    APIRequest request = <span class="keyword">new</span> APIRequest();</div><div class="line">    request.setAttribute(<span class="string">"username"</span>, <span class="string">"bingo"</span>);</div><div class="line">    request.setAttribute(<span class="string">"password"</span>, <span class="string">"123456"</span>);</div><div class="line">    APIResponse response = userService.login(request);</div><div class="line">    Assert.assertTrue(response.getMessage(), response.getState().equals(<span class="string">"00001"</span>));</div><div class="line"></div><div class="line">    <span class="comment">// 密码错误</span></div><div class="line">    request.setAttribute(<span class="string">"username"</span>, <span class="string">"bingo"</span>);</div><div class="line">    request.setAttribute(<span class="string">"password"</span>, <span class="string">"1234567"</span>);</div><div class="line">    response = userService.login(request);</div><div class="line">    Assert.assertTrue(response.getMessage(), response.getState().equals(<span class="string">"00002"</span>));</div><div class="line"></div><div class="line">    <span class="comment">// 用户名不存在</span></div><div class="line">    request.setAttribute(<span class="string">"username"</span>, <span class="string">"bingo1"</span>);</div><div class="line">    request.setAttribute(<span class="string">"password"</span>, <span class="string">"123456"</span>);</div><div class="line">    response = userService.login(request);</div><div class="line">    Assert.assertTrue(response.getMessage(), response.getState().equals(<span class="string">"00003"</span>));</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>测试通过，结合 APILogger 自动打印输出 API 日志：</p>
<figure class="highlight vim"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">[INFO][main][<span class="number">2017</span>-<span class="number">04</span>-<span class="number">12</span> <span class="number">21</span>:<span class="number">15</span>:<span class="number">09</span>][<span class="keyword">cn</span>.dankal.tools.api.APILogger] - [Params] password:<span class="number">123456</span></div><div class="line">[INFO][main][<span class="number">2017</span>-<span class="number">04</span>-<span class="number">12</span> <span class="number">21</span>:<span class="number">15</span>:<span class="number">09</span>][<span class="keyword">cn</span>.dankal.tools.api.APILogger] - [Params] username:bingo</div><div class="line">[INFO][main][<span class="number">2017</span>-<span class="number">04</span>-<span class="number">12</span> <span class="number">21</span>:<span class="number">15</span>:<span class="number">09</span>][<span class="keyword">cn</span>.dankal.tools.api.APILogger] - execution(APIResponse <span class="keyword">cn</span>.dankal.web.service.impl.UserServiceImpl.login(APIRequest)) Response: &#123;<span class="string">"state"</span>:<span class="string">"00001"</span>,<span class="string">"message"</span>:<span class="string">"success"</span>,<span class="string">"result"</span>:&#123;<span class="string">"id"</span>:<span class="number">1</span>,<span class="string">"username"</span>:<span class="string">"bingo"</span>,<span class="string">"enable"</span>:true,<span class="string">"role"</span>:<span class="string">"管理员"</span>,<span class="string">"last_time"</span>:<span class="string">"1483673751744"</span>,<span class="string">"create_time"</span>:<span class="string">"1483082840732"</span>&#125;&#125;</div><div class="line"></div><div class="line">[INFO][main][<span class="number">2017</span>-<span class="number">04</span>-<span class="number">12</span> <span class="number">21</span>:<span class="number">15</span>:<span class="number">09</span>][<span class="keyword">cn</span>.dankal.tools.api.APILogger] - [Params] password:<span class="number">1234567</span></div><div class="line">[INFO][main][<span class="number">2017</span>-<span class="number">04</span>-<span class="number">12</span> <span class="number">21</span>:<span class="number">15</span>:<span class="number">09</span>][<span class="keyword">cn</span>.dankal.tools.api.APILogger] - [Params] username:bingo</div><div class="line">[INFO][main][<span class="number">2017</span>-<span class="number">04</span>-<span class="number">12</span> <span class="number">21</span>:<span class="number">15</span>:<span class="number">09</span>][<span class="keyword">cn</span>.dankal.tools.api.APILogger] - execution(APIResponse <span class="keyword">cn</span>.dankal.web.service.impl.UserServiceImpl.login(APIRequest)) Response: &#123;<span class="string">"state"</span>:<span class="string">"00002"</span>,<span class="string">"message"</span>:<span class="string">"用户名密码错误"</span>&#125;</div><div class="line"></div><div class="line">[INFO][main][<span class="number">2017</span>-<span class="number">04</span>-<span class="number">12</span> <span class="number">21</span>:<span class="number">15</span>:<span class="number">09</span>][<span class="keyword">cn</span>.dankal.tools.api.APILogger] - [Params] password:<span class="number">123456</span></div><div class="line">[INFO][main][<span class="number">2017</span>-<span class="number">04</span>-<span class="number">12</span> <span class="number">21</span>:<span class="number">15</span>:<span class="number">09</span>][<span class="keyword">cn</span>.dankal.tools.api.APILogger] - [Params] username:bingo1</div><div class="line">[INFO][main][<span class="number">2017</span>-<span class="number">04</span>-<span class="number">12</span> <span class="number">21</span>:<span class="number">15</span>:<span class="number">09</span>][<span class="keyword">cn</span>.dankal.tools.api.APILogger] - execution(APIResponse <span class="keyword">cn</span>.dankal.web.service.impl.UserServiceImpl.login(APIRequest)) Response: &#123;<span class="string">"state"</span>:<span class="string">"00003"</span>,<span class="string">"message"</span>:<span class="string">"用户名不存在"</span>&#125;</div></pre></td></tr></table></figure>
<p>每个接口访问都自动输出了日志，请求参数和结果都一目了然，打印的连接点的信息也包含了 Service 层的 API 信息，基本满足了 API 系统 的日志需求。</p>
<h2 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h2><p>至此，整套写 API 的框架就整合配置完成了，测试效果符合预期。</p>
<p><img src="https://blog.bingo.ren/images/blog/36/demo.png" alt="Demo"></p>
<h2 id="后话"><a href="#后话" class="headerlink" title="后话"></a>后话</h2><p>Demo 源码已经托管到 <a href="https://github.com/bingozb/DKJavaWebApiDemo" target="_blank" rel="external">GitHub-DKJavaWebApiDemo</a>，遵循 MIT 开源协议。一方面作为个人的战斗记录，另一方面，也准备为公司的后台开创一个 JavaWeb 组，这是我今年的计划，还在评估阶段。如果这个设计对您有所帮助，希望能顺手点个 Star，谢谢！</p>

      
    </div>

    <div>
      
        

      
    </div>

    <div>
      
        

      
    </div>

    <div>
      
        
  <ul class="post-copyright">
    <li class="post-copyright-author">
      <strong>本文作者：</strong>
      Bingo
    </li>
    <li class="post-copyright-link">
      <strong>本文链接：</strong>
      <a href="https://bingozb.github.io/36.html" title="JavaWeb 基于Maven+SSM 提供API服务">https://bingozb.github.io/36.html</a>
    </li>
    <li class="post-copyright-license">
      <strong>版权声明： </strong>
      本博客所有文章除特别声明外，均采用 MIT 许可协议。转载请注明出处！
    </li>
  </ul>


      
    </div>

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/Java-EE/" rel="tag"># Java EE</a>
          
        </div>
      

      
        
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/37.html" rel="next" title="iOS项目的.gitignore配置">
                <i class="fa fa-chevron-left"></i> iOS项目的.gitignore配置
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/35.html" rel="prev" title="DKNetworking 网络层框架">
                DKNetworking 网络层框架 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          
  <div class="comments" id="comments">
    
      <div id="SOHUCS"></div>
    
  </div>


        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap" >
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
          <img class="site-author-image" itemprop="image"
               src="/images/avatar.jpg"
               alt="Bingo" />
          <p class="site-author-name" itemprop="name">Bingo</p>
           
              <p class="site-description motion-element" itemprop="description">He who hesitates is lost.</p>
          
        </div>
        <nav class="site-state motion-element">

          
            <div class="site-state-item site-state-posts">
              <a href="/archives">
                <span class="site-state-item-count">55</span>
                <span class="site-state-item-name">日志</span>
              </a>
            </div>
          

          
            
            
            <div class="site-state-item site-state-categories">
              <a href="/categories/index.html">
                <span class="site-state-item-count">4</span>
                <span class="site-state-item-name">分类</span>
              </a>
            </div>
          

          
            
            
            <div class="site-state-item site-state-tags">
              <a href="/tags/index.html">
                <span class="site-state-item-count">22</span>
                <span class="site-state-item-name">标签</span>
              </a>
            </div>
          

        </nav>

        
          <div class="feed-link motion-element">
            <a href="/atom.xml" rel="alternate">
              <i class="fa fa-rss"></i>
              RSS
            </a>
          </div>
        

        <div class="links-of-author motion-element">
          
            
              <span class="links-of-author-item">
                <a href="https://github.com/bingozb" target="_blank" title="GitHub">
                  
                    <i class="fa fa-fw fa-github"></i>
                  
                  GitHub
                </a>
              </span>
            
              <span class="links-of-author-item">
                <a href="https://coding.net/u/bingozb" target="_blank" title="Coding">
                  
                    <i class="fa fa-fw fa-git"></i>
                  
                  Coding
                </a>
              </span>
            
              <span class="links-of-author-item">
                <a href="https://twitter.com/bingo_v587" target="_blank" title="Twitter">
                  
                    <i class="fa fa-fw fa-twitter"></i>
                  
                  Twitter
                </a>
              </span>
            
              <span class="links-of-author-item">
                <a href="https://www.facebook.com/profile.php?id=100013406670597" target="_blank" title="Facebook">
                  
                    <i class="fa fa-fw fa-facebook"></i>
                  
                  Facebook
                </a>
              </span>
            
          
        </div>

        
        

        
        

        


      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#前言"><span class="nav-number">1.</span> <span class="nav-text">前言</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#规定与约束"><span class="nav-number">2.</span> <span class="nav-text">规定与约束</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#核心配置"><span class="nav-number">3.</span> <span class="nav-text">核心配置</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#添加依赖"><span class="nav-number">3.1.</span> <span class="nav-text">添加依赖</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Spring-MVC-配置"><span class="nav-number">3.2.</span> <span class="nav-text">Spring MVC 配置</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#开启注解模式驱动"><span class="nav-number">3.2.1.</span> <span class="nav-text">开启注解模式驱动</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#启动自动扫包"><span class="nav-number">3.2.2.</span> <span class="nav-text">启动自动扫包</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#删除视图解析器"><span class="nav-number">3.2.3.</span> <span class="nav-text">删除视图解析器</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Spring-配置"><span class="nav-number">3.3.</span> <span class="nav-text">Spring 配置</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#实现设计"><span class="nav-number">4.</span> <span class="nav-text">实现设计</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Json数据格式规范"><span class="nav-number">4.1.</span> <span class="nav-text">Json数据格式规范</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#正确结果的响应"><span class="nav-number">4.1.1.</span> <span class="nav-text">正确结果的响应</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#错误结果的响应"><span class="nav-number">4.1.2.</span> <span class="nav-text">错误结果的响应</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#字段说明"><span class="nav-number">4.1.3.</span> <span class="nav-text">字段说明</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#API工具类封装"><span class="nav-number">4.2.</span> <span class="nav-text">API工具类封装</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#APIStatus"><span class="nav-number">4.2.1.</span> <span class="nav-text">APIStatus</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#APIRequest"><span class="nav-number">4.2.2.</span> <span class="nav-text">APIRequest</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#APIResponse"><span class="nav-number">4.2.3.</span> <span class="nav-text">APIResponse</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#APIUtil"><span class="nav-number">4.2.4.</span> <span class="nav-text">APIUtil</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#APILogger"><span class="nav-number">4.2.5.</span> <span class="nav-text">APILogger</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Model层设计"><span class="nav-number">4.3.</span> <span class="nav-text">Model层设计</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Mapper层设计"><span class="nav-number">4.4.</span> <span class="nav-text">Mapper层设计</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Service层设计"><span class="nav-number">4.5.</span> <span class="nav-text">Service层设计</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#接口"><span class="nav-number">4.5.1.</span> <span class="nav-text">接口</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#实现"><span class="nav-number">4.5.2.</span> <span class="nav-text">实现</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Controller-层设计"><span class="nav-number">4.6.</span> <span class="nav-text">Controller 层设计</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#单元测试"><span class="nav-number">5.</span> <span class="nav-text">单元测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#效果"><span class="nav-number">6.</span> <span class="nav-text">效果</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#后话"><span class="nav-number">7.</span> <span class="nav-text">后话</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright" >
  
  Copyright © 
  <span itemprop="copyrightYear">2017
  </span>
  <span class="author" itemprop="copyrightHolder">Bingo
  </span>
  All rights reserved.
</div>
        

        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>

  
  <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>

  
  <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.1"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.1"></script>



  
  

  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.1"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.1"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.1"></script>



  


  




	





  





  





  




  
    <script type="text/javascript">
    (function(){
      var appid = 'cytcdzpa3';
      var conf = 'c0c6117021d1cea9f11629100efd8446';
      var width = window.innerWidth || document.documentElement.clientWidth;
      if (width < 960) {
      window.document.write('<script id="changyan_mobile_js" charset="utf-8" type="text/javascript" src="https://changyan.sohu.com/upload/mobile/wap-js/changyan_mobile.js?client_id=' + appid + '&conf=' + conf + '"><\/script>'); } else { var loadJs=function(d,a){var c=document.getElementsByTagName("head")[0]||document.head||document.documentElement;var b=document.createElement("script");b.setAttribute("type","text/javascript");b.setAttribute("charset","UTF-8");b.setAttribute("src",d);if(typeof a==="function"){if(window.attachEvent){b.onreadystatechange=function(){var e=b.readyState;if(e==="loaded"||e==="complete"){b.onreadystatechange=null;a()}}}else{b.onload=a}}c.appendChild(b)};loadJs("https://changyan.sohu.com/upload/changyan.js",function(){
        window.changyan.api.config({appid:appid,conf:conf})});
      }
    })();
    </script>
    <script type="text/javascript" src="https://assets.changyan.sohu.com/upload/plugins/plugins.count.js"></script>
  



  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (search_path.endsWith("json")) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      $('#local-search-input').focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('-1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  
  <script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.1.js"></script>
  <script>AV.initialize("A9MeTkIR49W9kOOSvkggsXge-gzGzoHsz", "HaIEOSDyxpcjwOjRjCYxiWjA");</script>
  <script>
    function showTime(Counter) {
      var query = new AV.Query(Counter);
      var entries = [];
      var $visitors = $(".leancloud_visitors");

      $visitors.each(function () {
        entries.push( $(this).attr("id").trim() );
      });

      query.containedIn('url', entries);
      query.find()
        .done(function (results) {
          var COUNT_CONTAINER_REF = '.leancloud-visitors-count';

          if (results.length === 0) {
            $visitors.find(COUNT_CONTAINER_REF).text(0);
            return;
          }

          for (var i = 0; i < results.length; i++) {
            var item = results[i];
            var url = item.get('url');
            var time = item.get('time');
            var element = document.getElementById(url);

            $(element).find(COUNT_CONTAINER_REF).text(time);
          }
          for(var i = 0; i < entries.length; i++) {
            var url = entries[i];
            var element = document.getElementById(url);
            var countSpan = $(element).find(COUNT_CONTAINER_REF);
            if( countSpan.text() == '') {
              countSpan.text(0);
            }
          }
        })
        .fail(function (object, error) {
          console.log("Error: " + error.code + " " + error.message);
        });
    }

    function addCount(Counter) {
      var $visitors = $(".leancloud_visitors");
      var url = $visitors.attr('id').trim();
      var title = $visitors.attr('data-flag-title').trim();
      var query = new AV.Query(Counter);

      query.equalTo("url", url);
      query.find({
        success: function(results) {
          if (results.length > 0) {
            var counter = results[0];
            counter.fetchWhenSave(true);
            counter.increment("time");
            counter.save(null, {
              success: function(counter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(counter.get('time'));
              },
              error: function(counter, error) {
                console.log('Failed to save Visitor num, with error message: ' + error.message);
              }
            });
          } else {
            var newcounter = new Counter();
            /* Set ACL */
            var acl = new AV.ACL();
            acl.setPublicReadAccess(true);
            acl.setPublicWriteAccess(true);
            newcounter.setACL(acl);
            /* End Set ACL */
            newcounter.set("title", title);
            newcounter.set("url", url);
            newcounter.set("time", 1);
            newcounter.save(null, {
              success: function(newcounter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(newcounter.get('time'));
              },
              error: function(newcounter, error) {
                console.log('Failed to create');
              }
            });
          }
        },
        error: function(error) {
          console.log('Error:' + error.code + " " + error.message);
        }
      });
    }

    $(function() {
      var Counter = AV.Object.extend("Counter");
      if ($('.leancloud_visitors').length == 1) {
        addCount(Counter);
      } else if ($('.post-title-link').length > 1) {
        showTime(Counter);
      }
    });
  </script>



  
<script>
(function(){
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
        bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';        
    }
    else {
        bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
})();
</script>


  

  

  

</body>
</html>
